springcloud

您所在的位置:网站首页 redis scan match很慢 springcloud

springcloud

2023-08-12 08:10| 来源: 网络整理| 查看: 265

业务场景

一次性取出redis一个field中的所有key,并遍历。

方案一

使用redisTemplate.opsForHash().keys("filed")

前期数据量少,未感知到性能问题。后查询资料得知,数据量上去后keys方法严重消耗CPU,一般在生产环境禁用keys方法。

防患于未然,该方式摒弃!

方案二

使用redisTemplate游标分批次获取

使用scan主要两个参数:match和count。

match:key的正则表达式

count:每次扫描的记录数。值越小,扫描次数越过、越耗时。建议设置在1000-10000

public void getKeysTest(){ try { Cursor cursor = deviceRedis.opsForHash().scan("filed", ScanOptions.scanOptions().match("*").count(1000).build()); while (cursor.hasNext()) { String key = cursor.next().getKey() Set valueSet = cursor.next().getValue(); } } catch (IOException e) { e.printStackTrace(); } }

使用scan代替keys肯定会导致整个查询消耗的总时间变大,但不会影响redis服务卡顿,影响服务使用。

后记

使用scan代替keys方式后,发现仅测试环境如此低的并发和数据量情况下,redis却经常会报错:

Could not get a resource from the pool !重启后回复正常,然后又重复!

问题定位

代码中对于redis的存、取、删除、过期设置、游标等操作都有涉及,最开始始终无法定位具体哪里的原因。

以为是RedisTemplate从JedisPool中获取连接使用后没有释放链接造成的。

但RedisTemplate是对Jedis做了二次封装,可自动通过连接池来管理连接。解读RedisTemplate源码后,确认了这一点:

RedisTemplate封装的redis操作方法,大多方法最底层都会调用最核心的excute方法,在excute方法的finally模块可以看到每次操作完成后都会自动关闭释放连接。该原因剔除!

最后通过调低连接池的max-active,多次的测试、重现,最终确认问题的原因是由于调用了scan后redis连接数只升不降。

通过redis-cli进入redis控制台,使用CLIENT LIST命令查看redis客户端连接信息:

其中,age标识已建立连接的时长(单位:秒),cmd标识操作命令。

正常情况下,redis连接信息中99%只应显示ping和auth命令,因为其他redis操作都是操作完成后立即释放连接,但是从上图看到scan命令对应连接的连接时长远高于其他操作,说明连接一直未断开。后来又测试几次,发现scan操作的连接信息只升不降。

最终问题终于定位le:scan操作后,连接没有释放,导致连接池可用连接被用完!

解决方式

又查看到scan的源码:

@Override public Cursor scan(K key, final ScanOptions options) { final byte[] rawKey = rawKey(key); return template.executeWithStickyConnection(new RedisCallback() { @Override public Cursor doInRedis(RedisConnection connection) throws DataAccessException { return new ConvertingCursor(connection.hScan(rawKey, options), new Converter() { @Override public Entry convert(final Entry source) { return new Map.Entry() { @Override public HK getKey() { return deserializeHashKey(source.getKey()); } @Override public HV getValue() { return deserializeHashValue(source.getValue()); } @Override public HV setValue(HV value) { throw new UnsupportedOperationException("Values cannot be set when scanning through entries."); } }; } }); } });

没有看到操作完成后关闭/释放/归还连接(或者我没找到吧)。

只能通过手动关闭来实现:

public void getKeysTest(){ try { Cursor cursor = deviceRedis.opsForHash().scan("filed", ScanOptions.scanOptions().match("*").count(1000).build()); while (cursor.hasNext()) { String key = cursor.next().getKey() Set valueSet = cursor.next().getValue(); } //关闭scan cursor.close(); } catch (IOException e) { e.printStackTrace(); } }

最后问题解决了,未再出现Could not get a resource from the pool!错误。

与各位共勉!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3